'''This module provides functions to determine whether a server is up or not.

    This can be hard to do well. What constitutes up? What about packet loss,
how long should we wait before we give up, et cetera.

    For now, two methods are provided. A wrapper for the 'ping' command, and a 
simple check for whether port 22 (ssh) is open.
'''

import commands

def ping(hostname, count=1, wait=5):
    '''Determine up status via ICMP ping.
    
    Returns True if hostname responded to a minimum of count pings over wait 
seconds. Returns False otherwise.
    Note on DNS lookup: on some networks ping('www.fkwejalkrbja.com') returns
True. I don't fully get the reason why - it somehow gets interpreted as a 
subdomain of the network. There is an easy fix: affixing the hostname with '.'
makes it an absolute path (or something) and it never gets lumped in as a 
non-existant subdomain. (Ex: ping('www.fkwejalkrbja.com.') fails properly.)

    This function is a thin wrapper for the ubiquitous (and eponymous) ping
command. A thorough review of ping is in order:

Typically, ping sends out ping requests every second.
    The `-c count` option makes it only send count packets. It then waits ~10 
seconds after the last packet is sent, for a reply. If no replies at all are 
received, ping exits with code 1. Code 0 indicates a reply was received. Code 2
indicates some other error. (Probably DNS lookup failure?)
    Adding the `-w wait` 'deadline' option makes it send 1 ping per second for
wait seconds. If less than count replies are received by the end, 1 is
returned. Other errors, 2. Success, 0.

    So, a decent metric for whether a server is up is to ping it for N seconds.
A minimum number of responses M must be received for it to count as 'up'. N and
M depend on whether packet loss is important or not; low M and high N make the
method very resilient to packet loss.

    I chose the default values (wait 5, count 1) to be reasonably quick, but 
optimistic with respect to packet loss.
    '''
    cmd = 'ping %s -c %d -w %d' % (hostname, count, wait)
    status,output = commands.getstatusoutput(cmd)
    return status==0

import socket

def has_ssh(hostname, timeout=5.0):
    '''Tries to connect to port 22.

This is excellent if our definition of 'up' is 'can be SSH'd into'. 

  Ideally, this could differentiate between servers that are down, and servers 
that are up but have no SSH running (and actively deny a connection).
  Can't see how, though. socket.error Errno 111 - connection refused is a
common reply, but so is simply timing out (google does that.)
  Going to stick with simple true/false for now.

    Same confusion about the . at the end of a name as with ping. No ., means
my home network assumes 'lkwejfalwkj' exists within it... Server ends up 
looking up. Why? How?
    '''
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(timeout) # we don't want to wait forever
    try:
        s.connect((hostname, 22))
    except socket.error:
        return False
    s.close()
    return True

import protocol.node
import client
def has_monitord(hostname):
    'Check if the host has monitord runing by trying to connect and ping.'
    try:
        s = client.connect(hostname)
        return client.ask(s, protocol.node.ping())=='pong'
    except:
        return False
